﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using Vnoss.Language.French.Models;
using System.Collections;


namespace Vnoss.Language.French.DAL
{
    /// <summary>
    /// Accès aux données pour la conjugaison.
    /// </summary>
    public class ConjugationDAO : IConjugationDAO
    {
        /// <summary>
        /// Pour naviguer dans l'arborescence des templates (règles) de conjugaison
        /// </summary>
        private XPathNavigator _conjNav;
        /// <summary>
        /// Pour navigeur dans la liste des verbes et leur template.
        /// </summary>
        private XPathNavigator _verbNav;

        /// <summary>
        /// Caching
        /// </summary>
        private static LruCache _cache;
      
        /// <summary>
        /// XPathNavigator pour naviguer dans le fichier XML des règles de conjugaison
        /// </summary>
        private XPathNavigator ConjugationNavigator
        {
            get { return _conjNav; }
        }
        /// <summary>
        /// XPathNavigator pour naviguer dans le fichier XML des verbes français
        /// </summary>
        private XPathNavigator VerbNavigator
        {
            get { return _verbNav; }
        }

        /// <summary>
        /// Construteur par défaut
        /// </summary>
        public ConjugationDAO()
        {
            //Initialiser les XPathNavigator
            var xdoc = new XPathDocument(Assembly.GetExecutingAssembly().GetManifestResourceStream("Vnoss.Language.French.Data.conjugation-fr.xml"));
            _conjNav = xdoc.CreateNavigator();
            xdoc = new XPathDocument(Assembly.GetExecutingAssembly().GetManifestResourceStream("Vnoss.Language.French.Data.verbs-fr.xml"));
            _verbNav = xdoc.CreateNavigator();
            if (_cache == null)
                _cache = new LruCache();

        }



        /// <summary>
        /// Recherche d'une template (i.e: des règles de conjugaison) pour une verbe
        /// </summary>
        /// <param name="infinitif">le forme infinitif du verbe</param>
        /// <returns>le nom du template</returns>
        private string FindTemplateFromVerb(string infinitive)
        {
            string template = _cache[infinitive] as string;
            if (template != null)
                return template;

            template = VerbNavigator.SelectSingleNode(string.Format("/verbs-fr/v[i=\"{0}\"]/t", infinitive)).Value;
            _cache[infinitive] = template;
            return template;

        }

        /// <summary>
        /// Indiquer si le verbe existe dans la base
        /// </summary>
        /// <param name="infinitive"></param>
        /// <returns></returns>
        public bool HasVerb(string infinitive)
        {
            string key = string.Concat("Has@", infinitive);
            if (_cache[key] == null)
            {
                bool res;
                if (string.IsNullOrEmpty(infinitive))
                    res = false;
                else if (!IsValid(infinitive))
                    res = false;
                else if (VerbNavigator.Select(string.Format("/verbs-fr/v[i='{0}']", infinitive)).Count == 1)
                {
                    res = true;
                }
                else res = false;

                _cache[key] = res;
                return res;
            }
            else // déjà validé
                return (bool)_cache[key];
        }
        /// <summary>
        /// Test si la chaîne ne contient pas des caratères spéciaux
        /// </summary>
        /// <returns></returns>
        private bool IsValid(string s)
        {
            if (String.IsNullOrEmpty(s))
                return true;
            return (@" &~#'{([|`\^@)]=}+$%*?.,;:/!<>1234567890".IndexOfAny(s.ToCharArray()) < 0);
        }

        /// <summary>
        /// Rercherche tous les terminaisons possibles d'une template à partir de son mode et son temps
        /// </summary>
        /// <param name="template">Template du verb à recherche</param>
        /// <param name="mode">mode du verbe</param>
        /// <param name="temps">temps du verbe</param>
        /// <returns>une liste des listes des terminaisons possibles pour chaque personne </returns>
        private List<List<string>> FindTerminalFromTemplate(string template, Mood mood, Tense tense)
        {
            List<List<string>> res;
            string key = string.Format("{0}@{1}@{2}", template, mood, tense);

            res = _cache[key] as List<List<string>>;
            if (res != null)
                return res;

            res = new List<List<string>>();


            StringBuilder xpath = new StringBuilder();

            #region Recherche le xpath pour la mode et le temps
            xpath.AppendFormat("/conjugation-fr/template[@name=\"{0}\"]", template);

            switch (mood)
            {
                case Mood.Infinitive:
                    xpath.Append("/infinitive");
                    switch (tense)
                    {
                        case Tense.Present:
                        case Tense.Indefinite:
                            xpath.Append("/infinitive-present");
                            break;
                        default: // Il n'y a pas de temps pour infinitif
                            return null;
                    }
                    break;
                case Mood.Indicative:
                    xpath.Append("/indicative");
                    switch (tense)
                    {
                        case Tense.Present:
                            xpath.Append("/present");
                            break;
                        case Tense.Imperfect:
                            xpath.Append("/imperfect");
                            break;
                        case Tense.Future:
                            xpath.Append("/future");
                            break;
                        case Tense.SimplePast:
                            xpath.Append("/simple-past");
                            break;
                        default: return null;
                    }
                    break;
                case Mood.Conditional:
                    xpath.Append("/conditional");
                    switch (tense)
                    {
                        case Tense.Present:
                            xpath.Append("/present");
                            break;
                        default: // Temps impossible
                            return null;
                    }
                    break;
                case Mood.Subjunctive:
                    xpath.Append("/subjunctive");
                    switch (tense)
                    {
                        case Tense.Present:
                            xpath.Append("/present");
                            break;
                        case Tense.Imperfect:
                            xpath.Append("/imperfect");
                            break;
                        default: // Temps impossible
                            return null;
                    }
                    break;
                case Mood.Imperative:
                    xpath.Append("/imperative");
                    switch (tense)
                    {
                        case Tense.Indefinite:
                        case Tense.Present:
                            xpath.Append("/imperative-present");
                            break;
                        default: // Temps impossible
                            return null;
                    }
                    break;
                case Mood.Participle:
                    xpath.Append("/participle");
                    switch (tense)
                    {
                        case Tense.Present:
                            xpath.Append("/present-participle");
                            break;
                        case Tense.Past:
                            xpath.Append("/past-participle");
                            break;
                        default: // Temps impossible
                            return null;
                    }
                    break;
                default:
                    return null;
            }
            #endregion
            xpath.Append("/p");
            try
            {
                var xiter = _conjNav.Select(xpath.ToString());
                List<string> termPerPerson;
                // Pour chaque personne
                while (xiter.MoveNext())
                {
                    termPerPerson = new List<string>();
                    // On peut avoir plusiers terminaison pour la même personne
                    var child = xiter.Current.Select("i");
                    while (child.MoveNext())
                    {
                        termPerPerson.Add(child.Current.Value);
                    }
                    res.Add(termPerPerson);

                }
            }
            catch //s'il y en a
            {
                return null;
            }

            _cache[key] = res;
            return res;
        }


        /// <summary>
        /// Recherche l'orgine d'une verbe à l'aide de sa template
        /// </summary>
        /// <param name="infinitif"></param>
        /// <param name="template"></param>
        /// <returns></returns>
        private string FindOriginFromTemplate(string infinitive, string template)
        {
            string end = template.Substring(template.IndexOf(":") + 1);
            int pos = infinitive.LastIndexOf(end);
            if (pos < 0)
                throw new IndexOutOfRangeException(string.Format("Cannot find end of the verb {0} from with template {1}", infinitive, template));
            return infinitive.Remove(pos);
        }
        /// <summary>
        /// Recherche d'une mode à partir de sa présentation en chaîne de caratère
        /// </summary>
        /// <param name="mode">le mode en chaîne de caractère</param>
        /// <returns>l'énum du mode</returns>
        [Obsolete]
        private Mood GetMoodFromString(string mood)
        {
            switch (mood)
            {
                case "indicative":
                    return Mood.Indicative;
                case "conditional":
                    return Mood.Conditional;
                case "subjunctive":
                    return Mood.Subjunctive;
                case "imperative":
                    return Mood.Imperative;
                case "participle":
                    return Mood.Participle;
                default:
                    return Mood.Indefinite;
            }
        }

        /// <summary>
        /// Recherche l'énum du temps à l'aide de sa présentation en chaîne de caratères
        /// </summary>
        /// <param name="temps">le temps en chaîne de caratères</param>
        /// <returns>l'énume du temps</returns>
        [Obsolete]
        private Tense GetTenseFromString(string tense)
        {
            switch (tense)
            {
                case "present":
                    return Tense.Present;
                case "imperfect":
                    return Tense.Imperfect;
                case "future":
                    return Tense.Future;
                case "simple-past":
                    return Tense.SimplePast;
                case "present-participle":
                    return Tense.Present;
                case "past-participle":
                    return Tense.Past;
                case "imperative-present":
                    return Tense.Present;
                default:
                    return Tense.Indefinite;
            }
        }

        #region IConjugaisonDAO

        /// <summary>
        /// Recherche l'origine, cad la partie unchangée du verbe, pour la conjugaison
        /// </summary>
        /// <param name="infinitive">Le verbe en infinitif</param>
        /// <returns>La partie fixe du verbe</returns>
        public string FindOrigin(string infinitive)
        {
            if (!HasVerb(infinitive))
                return null; //Verbe invalide
            string template = FindTemplateFromVerb(infinitive);
            if (template == null)
                return null;
            return FindOriginFromTemplate(infinitive, template);
        }
        /// <summary>
        /// Recherche des terminaisons d'un verbe en mode et temps donnés
        /// </summary>
        /// <param name="infinitive">Verbe en infinitif</param>
        /// <param name="mood">le mode du verbe</param>
        /// <param name="tense">le temps du verbe</param>
        /// <returns>Une liste des liste de terminaison. Chaque liste élémentaire correspondante à une personne. Pour un temp, un mode, une personne donnée peux avoir aucune ou plusiers terminaisons</returns>
        public List<List<string>> FindTerminal(string infinitive, Mood mood, Tense tense)
        {
            if (!HasVerb(infinitive))
                return null; //Verbe n'est pas validé.
            string template = FindTemplateFromVerb(infinitive);
            if (template == null)
                return null;
            return FindTerminalFromTemplate(template, mood, tense);
        }

        /// <summary>
        /// Recherche tous les verbes commencés par le préfix
        /// </summary>
        /// <param name="prefix">Préfix</param>
        /// <param name="maxResult">Nombre maximum des résultats</param>
        /// <remarks>Si le préfix est rien, retourner la liste de tous le verbes dans la base</remarks>
        /// <returns>Liste des verbes sous forme de chaîne de caratères satifaisant</returns>
        public List<string> FindVerbs(string prefix, int maxResult)
        {

            string xPath = "";
            //Recherche tous les verbes;
            if (string.IsNullOrEmpty(prefix))
            {
                xPath = "/verbs-fr/v/i";
            }
            else if (!IsValid(prefix))
                return null;
            else //recherche juste une partie commencé par le préfix
            {
                xPath = string.Format("/verbs-fr/v/i[starts-with(text(),'{0}')]", prefix);
            }
            List<string> res = new List<string>();
            XPathNodeIterator xiter = VerbNavigator.Select(xPath);
            if (xiter != null)
                while (xiter.MoveNext() && xiter.CurrentPosition < maxResult)
                {
                    res.Add(xiter.Current.Value);
                }
            else
                return null;
            return res;
        }

        
        /// <summary>
        /// Compter nombre des verbes dans la base
        /// </summary>
        /// <returns></returns>
        public int CountVerb()
        {
            return VerbNavigator.Select("/verbs-fr/v").Count;
        }

        /// <summary>
        /// Compter le nombre des verbes commencés par le préfix
        /// </summary>
        /// <param name="prefix">le préfix du verb</param>
        /// <returns>Nombre des verbes commencés par le préfix</returns>
        /// <remarks>Pour la raison de performance, cette méthode est conseillée pour compter le nombre de verbes, si la liste des verbes n'est pas important. <br />Si le préfix est rien, retourner le nombre total des verbes dans la base</remarks>
        public int CountVerb(string prefix)
        {
            if (string.IsNullOrEmpty(prefix))
                return CountVerb();

            if (!IsValid(prefix))
                return 0;
            return VerbNavigator.Select(string.Format("/verbs-fr/v/i[starts-with(text(),'{0}')]", prefix)).Count;

        }

        /// <summary>
        /// Indiquer si le verbe est commencé par un H muet. Utile pour la mise en forme.
        /// </summary>
        /// <param name="infinitive">verbe infinitif</param>
        /// <returns></returns>
        public bool IsMuteH(string infinitive)
        {
            if (!HasVerb(infinitive))
                return false;
            else
                if (!infinitive.StartsWith("h"))
                    return false;
                else
                    return VerbNavigator.SelectSingleNode(string.Format("/verbs-fr/v[i='{0}']/aspirate-h", infinitive)) == null ? true : false;
        }


        /// <summary>
        /// Rercherche le groupe du verbe
        /// </summary>
        /// <param name="infinitive">l'infinitif du verbe</param>
        /// <returns>Group du verbe</returns>
        public Group FindGroup(string infinitive)
        {
            if (!HasVerb(infinitive)) //Sais trop quoi faire!
                return (Group)1;
            string key = string.Concat("g@", infinitive);
            if (_cache[key] != null)
                return (Group)_cache[key];

            Group group = (Group)VerbNavigator.SelectSingleNode(string.Format("/verbs-fr/v[i='{0}']/@g", infinitive)).ValueAsInt;

            _cache[key] = group;

            return group;

        }

        /// <summary>
        /// Recherche l'auxiliaire du verbe, pour le temps et mode composé
        /// </summary>
        /// <param name="infinitive">l'infinitif du verbe</param>
        /// <remarks>L'auxiliaire peut avoir la valeur "avoir", "être" ou les deux</remarks>
        /// <returns>liste des auxiliaires</returns>
        public IList<string> FindAuxiliary(string infinitive)
        {

            if (!HasVerb(infinitive)) //Sais trop quoi faire!
                return null;

            string key = string.Concat("a@", infinitive);

            IList<string> res = _cache[key] as IList<string>;

            if (res != null)
                return res;
            res = new List<string> ();
            XPathNodeIterator iter = VerbNavigator.Select(string.Format("/verbs-fr/v[i='{0}']/a", infinitive));
            if (iter.Count ==0)
            {
                res.Add("avoir"); // par défaut, auxiliaire est avoir
            }
            else {
                while (iter.MoveNext()){
                    res.Add(iter.Current.Value);
                }
            }
            _cache[key] = res;

            return res;
        }
        #endregion



    }
}
